/*
 * $Id: thr.c,v 1.6 2006-03-29 11:47:46 bacon Exp $
 */

#include <xp/bas/thr.h>
#include <xp/bas/memory.h>
#include <xp/bas/signal.h>
#include <xp/bas/time.h>
#include <xp/bas/stdarg.h>

xp_thr_t* xp_thr_open (
	xp_thr_t* thr, int (*routine) (xp_thr_t*), void* user_data)
{
	if (thr == XP_NULL) {
		thr = (xp_thr_t*) xp_malloc (xp_sizeof(xp_thr_t));
		if (thr == XP_NULL) return XP_NULL;
		thr->__dynamic = xp_true;
	}
	else {
		thr->__dynamic = xp_false;
	}

	thr->__user_data = user_data;
	thr->__handle = XP_THRHND_INVALID;
	thr->__state = XP_THR_INCUBATING;
	thr->__return_code = 0;
	thr->__main_routine = routine;
	thr->__joinable = xp_true;

	return thr;	
}

void xp_thr_close (xp_thr_t* thr)
{
#ifdef _WIN32
	if (thr->__handle != XP_THRHND_INVALID) {
		CloseHandle (thr->__handle);
	}
#endif

	thr->__handle = XP_THRHND_INVALID;
	if (thr->__dynamic) {
		xp_free (thr);
		thr->__dynamic = xp_false;
	}
}

#if defined(__BEOS__)
static int32 __thread_main (void* arg)
#else
static void* __thread_main (void* arg)
#endif
{
	xp_thr_t* thr = (xp_thr_t*)arg;

	while (thr->__state != XP_THR_RUNNING) xp_sleep (100);

#if !defined(_WIN32) && !defined(__BEOS__)
	/* 
	 * the asynchronous cancel-type is used to better emulate
	 * the bad effect of WIN32's TerminateThread using pthread_cancel 
	 */
	pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, XP_NULL);
	pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, XP_NULL);
#endif

	thr->__return_code = (thr->__temp_routine != XP_NULL)? 
		thr->__temp_routine (thr): thr->__main_routine (thr);
	thr->__state = XP_THR_TERMINATED;

#if defined(_WIN32)
	_endthreadex (thr->__return_code);
	return XP_NULL;
#elif defined(__BEOS__)
	exit_thread (thr->__return_code);
	return 0;
#else
	pthread_exit (&thr->__return_code);
	return XP_NULL;
#endif
}

static int __create_thread (xp_thr_t* thr)
{
#if defined(_WIN32)
	unsigned int tid;

	if (thr->__handle != XP_THRHND_INVALID) {
		CloseHandle (thr->__handle);
	}

	thr->__handle = (HANDLE)_beginthreadex (XP_NULL, 0,
		(unsigned int (__stdcall*)(void*))__thread_main, thr, 0, &tid);
	if (thr->__handle == 0) return -1;
#elif defined(__BEOS__)
	thread_id tid;

	tid = spawn_thread ((thread_func)__thread_main, XP_NULL, 120, thr);
	if (tid < B_OK) return -1;

	thr->__handle = tid;
	resume_thread(thr->__handle);
#else
	pthread_attr_t attr;
	pthread_attr_init (&attr);

	if (pthread_attr_setdetachstate (&attr, (thr->__joinable? 
		PTHREAD_CREATE_JOINABLE: PTHREAD_CREATE_DETACHED)) != 0) return -1;

	if (pthread_create (&thr->__handle, &attr, __thread_main, thr) != 0) {
		pthread_attr_destroy (&attr);
		return -1;
	}

	pthread_attr_destroy (&attr);
#endif
	return 0;
}

static int __cancel_thread (xp_thr_t* thr)
{
	if (thr->__state != XP_THR_RUNNING) return -1;
#if defined(_WIN32)
	if (TerminateThread (thr->__handle, 0) == 0) return -1;
#elif defined(__BEOS__)
	if (kill_thread (thr->__handle) < B_OK) return -1;
#else
	if (pthread_cancel (thr->__handle) != 0) return -1;
#endif
	return 0;
}

int xp_thr_start (xp_thr_t* thr, int flags, ...)
{
	if (thr->__state == XP_THR_RUNNING) return -1;

	thr->__joinable = ((flags & XP_THR_DETACHED) == 0);
	if (flags & XP_THR_NEW_ROUTINE) {
		xp_va_list va;
		xp_va_start (va, flags);
		thr->__temp_routine = xp_va_arg (va, xp_thr_routine_t);
		xp_va_end (va);
	}
	else thr->__temp_routine = XP_NULL;

	if (__create_thread(thr) == -1) {
		thr->__state = XP_THR_INCUBATING;
		thr->__handle = XP_THRHND_INVALID;
		return -1;
	}

	thr->__state = XP_THR_RUNNING;
	return 0;
}

int xp_thr_stop (xp_thr_t* thr)
{
	if (thr->__state == XP_THR_RUNNING) {
		if (__cancel_thread(thr) == -1) return -1;
		/* can't be sure of whether or not the thread is really terminated. */
 		thr->__state = XP_THR_ABORTED;
		return 0;
	}

	return -1;
}

int xp_thr_join (xp_thr_t* thr)
{
	if (thr->__state == XP_THR_INCUBATING) return -1;
	if (!thr->__joinable) return -1;

#if defined(_WIN32)
	if (thr->__state == XP_THR_RUNNING) {
		if (WaitForSingleObject (
			thr->__handle, INFINITE) == WAIT_FAILED) return -1;
	}
#elif defined(__BEOS__)
	if (wait_for_thread(thr->__handle, XP_NULL) < B_OK) return -1;
#else
	if (pthread_join(thr->__handle, XP_NULL) != 0) return -1;
#endif

	thr->__joinable = xp_false;
	return 0;
}

int xp_thr_detach (xp_thr_t* thr)
{
	if (thr->__state == XP_THR_INCUBATING) return -1;
	if (!thr->__joinable) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	if (pthread_detach(thr->__handle) != 0) return -1;
#endif

	thr->__joinable = xp_false;
	return 0;
}

int xp_thr_kill (xp_thr_t* thr, int sig)
{
	if (thr->__state != XP_THR_RUNNING) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	if (pthread_kill (thr->__handle, sig) != 0) return -1;
#endif
	return 0;
}

int xp_thr_blocksig (xp_thr_t* thr, int sig)
{
#if !defined(_WIN32) && !defined(__BEOS__)
	sigset_t mask;
#endif

	if (thr->__state != XP_THR_RUNNING) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	sigemptyset (&mask);
	sigaddset (&mask, sig);
	if (pthread_sigmask (SIG_BLOCK, &mask, XP_NULL) != 0) return -1;
#endif
	return 0;
}

int xp_thr_unblocksig (xp_thr_t* thr, int sig)
{
#if !defined(_WIN32) && !defined(__BEOS__)
	sigset_t mask;
#endif

	if (thr->__state != XP_THR_RUNNING) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	sigemptyset (&mask);
	sigaddset (&mask, sig);
	if (pthread_sigmask (SIG_UNBLOCK, &mask, XP_NULL) != 0) return -1;
#endif
	return 0;
}

int xp_thr_blockallsigs (xp_thr_t* thr)
{
#if !defined(_WIN32) && !defined(__BEOS__)
	sigset_t mask;
#endif

	if (thr->__state != XP_THR_RUNNING) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	sigfillset (&mask);
	if (pthread_sigmask (SIG_BLOCK, &mask, XP_NULL) != 0) return -1;
#endif
	return 0;
}

int xp_thr_unblockallsigs (xp_thr_t* thr)
{
#if !defined(_WIN32) && !defined(__BEOS__)
	sigset_t mask;
#endif

	if (thr->__state != XP_THR_RUNNING) return -1;
	
#if !defined(_WIN32) && !defined(__BEOS__)
	sigfillset (&mask);
	if (pthread_sigmask (SIG_UNBLOCK, &mask, XP_NULL) != 0) return -1;
#endif
	return 0;
}

xp_thrhnd_t xp_thr_gethnd (const xp_thr_t* thr)
{
	return thr->__handle;
}

int xp_thr_getretcode (const xp_thr_t* thr)
{
	return thr->__return_code;
}

int xp_thr_getstate (const xp_thr_t* thr)
{
	return thr->__state;
}

void* xp_thr_getuserdata (const xp_thr_t* thr)
{
	return (void*)thr->__user_data;
}

xp_thrhnd_t xp_getcurthrhnd (void)
{
#if defined(_WIN32)
	return GetCurrentThread ();
#elif defined(__BEOS__)
	return XP_THRHND_INVALID; /* TODO: implement this */
#else
	return pthread_self ();
#endif
}
